package com.wechat.pay.api.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.api.config.PayUrlConfig;
import com.wechat.pay.api.config.WxConfig;
import com.wechat.pay.api.dao.OrderDao;
import com.wechat.pay.api.dao.UserDao;
import com.wechat.pay.api.enums.TradeStateEnum;
import com.wechat.pay.api.exception.CustomException;
import com.wechat.pay.api.vo.EncryptMessageVo;
import com.wechat.pay.api.vo.OrderInfo;
import com.wechat.pay.api.vo.UserInfo;
import com.wechat.pay.api.vo.payNotify.NoticeVo;
import com.wechat.pay.api.vo.payNotify.PaySuccessNoticeVo;
import com.wechat.pay.api.vo.refund.RefundOrderAmount;
import com.wechat.pay.api.vo.refund.RefundOrderParam;
import com.wechat.pay.api.vo.refundNotify.RefundSuccessNoticeVo;
import com.wechat.pay.api.vo.refundNotify.RefundSuccessVo;
import com.wechat.pay.api.vo.unifiedOrder.UnifiedOrderAmount;
import com.wechat.pay.api.vo.unifiedOrder.UnifiedOrderParam;
import com.wechat.pay.api.vo.unifiedOrder.UnifiedOrderPayer;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;

/**
 * 不需要签名和验签
 */
@Slf4j
@Service
public class WxPayService {
    @Resource
    private WxConfig wxConfig;
    @Resource
    private UserDao userDao;
    @Resource
    private OrderDao orderDao;

    /**
     * 统一下单
     *
     * @param userId   用户id
     * @param payTotal 支付金额
     * @return 小程序调起微信支付控件的参数
     * @throws Exception 异常
     */
    public Map<String, String> unifiedOrder(Long userId, int payTotal) throws Exception {
        UnifiedOrderParam param = getUnifiedOrderParam(userId, payTotal, UUID.randomUUID().toString().replace("-", ""));
        HttpResponse response = post(PayUrlConfig.UNIFIED_ORDER, JSONObject.toJSONString(param));
        String bodyAsString = EntityUtils.toString(response.getEntity());
        JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
        String prepay_id = jsonObject.get("prepay_id").toString();
        if (StringUtils.isBlank(prepay_id)) {
            throw new CustomException("prepay_id 为空");
        }
        return getWxPayParam(prepay_id);
    }

    /**
     * 统一下单参数拼接
     *
     * @param userId   用户id
     * @param payTotal 支付金额
     * @param orderId  订单号
     * @return 统一下单参数
     */
    private UnifiedOrderParam getUnifiedOrderParam(Long userId, int payTotal, String orderId) {
        Optional<UserInfo> wsWxUserInfoOptional = userDao.queryByUserId(userId);

        if (!wsWxUserInfoOptional.isPresent()) {
            throw new CustomException("非法用户！");
        }
        UserInfo wsWxUserInfo = wsWxUserInfoOptional.get();
        // 订单金额
        UnifiedOrderAmount amount = UnifiedOrderAmount.builder().total(payTotal).currency("CNY").build();
        //支付者
        UnifiedOrderPayer payer = UnifiedOrderPayer.builder().openid(wsWxUserInfo.getOpenId()).build();
        return UnifiedOrderParam
                .builder()
                .appid(wxConfig.getAppId())
                .mchid(wxConfig.getMchId())
                .description("商品描述")
                .out_trade_no(orderId)
                .notify_url(wxConfig.getPayNotifyUrl())
                .amount(amount)
                .payer(payer)
                .build();
    }

    /**
     * 拼接小程序调起微信支付控件的参数
     *
     * @param prePayId 预支付编码
     * @return 参数
     * @throws Exception 异常
     */
    private Map<String, String> getWxPayParam(String prePayId) throws Exception {
        String nonceStr = RandomStringUtils.randomAlphanumeric(32);
        long timestamp = System.currentTimeMillis() / 1000;
        //包装prepay_id 等 返给前端由前端拉起支付
        HashMap<String, String> params = new HashMap<>();
        params.put("appId", wxConfig.getAppId());
        params.put("nonceStr", nonceStr);
        params.put("package", "prepay_id=" + prePayId);
        params.put("signType", "RSA");
        params.put("timeStamp", String.valueOf(timestamp));
        //重新签名
        String signMessage = spliceNewline(wxConfig.getAppId(), timestamp, nonceStr, "prepay_id=" + prePayId);
        String paySign = sign(signMessage.getBytes(StandardCharsets.UTF_8));
        params.put("paySign", paySign);
        return params;
    }

    /**
     * 支付回调（回调接口和获取平台证书的接口都需要做解密）
     *
     * @param request HttpServletRequest
     * @throws Exception 异常
     */
    public void wxPayNotify(HttpServletRequest request) throws Exception {
        PaySuccessNoticeVo paySuccessNoticeVo = buildCallbackResult(request, PaySuccessNoticeVo.class);
        //根据支付状态做不同的业务处理
        if (TradeStateEnum.SUCCESS.getCode().equals(paySuccessNoticeVo.getTrade_state())) {
            //todo 支付成功业务逻辑，如将订单状态更新为已支付
        } else if (TradeStateEnum.USER_PAYING.getCode().equals(paySuccessNoticeVo.getTrade_state())) {
            //todo 支付中业务
        }
    }

    /**
     * 申请退款
     *
     * @param userId  用户id
     * @param orderNo 商户订单号
     * @throws Exception 异常
     */
    public void wxRefund(Long userId, String orderNo) throws Exception {
        //查询订单信息
        Optional<OrderInfo> optional = orderDao.findOrder(userId, orderNo);
        if (!optional.isPresent()) {
            throw new CustomException("无效订单");
        }
        if (optional.get().getOrderStatus() != 0) {//可退款状态
            throw new CustomException("订单处于不可退款状态!!!,请联系客服.");
        }

        //执行退款
        RefundOrderParam param = getRefundOrderParam();
        HttpResponse response = post(PayUrlConfig.UNIFIED_ORDER, JSONObject.toJSONString(param));
        String bodyAsString = EntityUtils.toString(response.getEntity());
        RefundSuccessVo refundSuccessVo = JSON.parseObject(bodyAsString, RefundSuccessVo.class);
        if ("SUCCESS".equals(refundSuccessVo.getStatus())) {
            //todo 申请退款成功业务逻辑
        }
    }

    /**
     * 退款参数拼接
     *
     * @return 退款参数
     */
    private RefundOrderParam getRefundOrderParam() {
        // 退款金额
        RefundOrderAmount amount = RefundOrderAmount
                .builder()
                //原订单金额 去订单表查询此次订单金额
                .total(0)
                //退款金额 实际退款金额 根据业务需求计算
                .refund(0)
                .currency("CNY")
                .build();
        return RefundOrderParam
                .builder()
                .transaction_id("微信支付订单号")
                .out_trade_no("商户订单号")
                .out_refund_no("商户退款单号")
                .reason("用户退款")
                //退款回调接口地址
                .notify_url(wxConfig.getRefundNotifyUrl())
                .funds_account("AVAILABLE")
                .amount(amount)
                .build();
    }

    /**
     * 退款回调
     *
     * @param request HttpServletRequest
     * @throws Exception 异常
     */
    public void wxRefundNotify(HttpServletRequest request) throws Exception {
        RefundSuccessNoticeVo refundSuccessNoticeVo = buildCallbackResult(request, RefundSuccessNoticeVo.class);
        if ("SUCCESS".equals(refundSuccessNoticeVo.getRefund_status())) {
            //todo 退款成功业务逻辑处理
        }
    }

    /**
     * 获取平台证书列表
     *
     * @return 平台证书列表
     * @throws Exception 异常
     */
    public String getPlatformCertificateList() throws Exception {
        HttpResponse httpResponse = get(PayUrlConfig.GET_CERTIFICATE_LIST);
        JSONObject response = JSON.parseObject(EntityUtils.toString(httpResponse.getEntity()));
        JSONArray resultArr = (Objects.isNull(response) || Objects.isNull(response.get("data"))) ? new JSONArray() : JSONObject.parseArray(response.get("data").toString());
        JSONObject result = JSON.parseObject((Objects.isNull(resultArr) || Objects.isNull(resultArr.get(0))) ? StringUtils.EMPTY : resultArr.get(0).toString());
        EncryptMessageVo resource = JSON.parseObject(JSON.toJSONString(result.get("encrypt_certificate")), EncryptMessageVo.class);
        AesUtil aesUtil = new AesUtil(wxConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        return aesUtil.decryptToString(resource.getAssociated_data().getBytes(StandardCharsets.UTF_8),
                resource.getNonce().getBytes(StandardCharsets.UTF_8),
                resource.getCiphertext());
    }

    /**
     * 解析request中的数据
     *
     * @param request HttpServletRequest
     * @return request中的数据
     */
    private String getRequestBody(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (ServletInputStream stream = request.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (Exception e) {
            log.error("failed e={}", e.getMessage());
            throw new CustomException("操作失败");
        }
        return sb.toString();
    }

    /**
     * 处理回调接口返回结果
     *
     * @param request HttpServletRequest
     * @param clazz   结果类型
     * @return 回调接口返回结果
     * @throws Exception 异常
     */
    private <T> T buildCallbackResult(HttpServletRequest request, Class<T> clazz) throws Exception {
        NoticeVo noticeVo = JSON.parseObject(getRequestBody(request), NoticeVo.class);
        EncryptMessageVo resource = noticeVo.getResource();
        AesUtil aesUtil = new AesUtil(wxConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String result = aesUtil.decryptToString(resource.getAssociated_data().getBytes(StandardCharsets.UTF_8),
                resource.getNonce().getBytes(StandardCharsets.UTF_8),
                resource.getCiphertext());
        return JSON.parseObject(result, clazz);
    }

    /**
     * 拼接换行符
     *
     * @param param 参数
     * @return 拼接后的结果
     */
    private String spliceNewline(Object... param) {
        StringBuilder result = new StringBuilder();
        for (Object str : param) {
            result.append(str).append(StringUtils.LF);
        }
        return result.toString();
    }

    /**
     * 生成签名
     *
     * @param message 签名信息
     * @return 签名
     * @throws Exception 异常
     */
    private String sign(byte[] message) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey());
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    /**
     * post请求
     *
     * @param url      url地址
     * @param jsonBody 参数体
     * @return 返回结果
     */
    private HttpResponse post(String url, String jsonBody) throws Exception {
        URIBuilder uriBuilder = new URIBuilder(url);
        HttpPost httpPost = new HttpPost(uriBuilder.build());
        //header封装
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-Type", "application/json");
        StringEntity entity = new StringEntity(jsonBody, StandardCharsets.UTF_8);
        httpPost.setEntity(entity);
        //执行方法
        HttpResponse response = buildHttpClientAutoUpdateCertificate().execute(httpPost);
        //状态判断
        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
            throw new CustomException("操作失败");
        }
        return response;
    }

    /**
     * get请求
     *
     * @param url 访问url
     * @return 返回结果
     * @throws Exception 异常
     */
    private HttpResponse get(String url) throws Exception {
        URIBuilder uriBuilder = new URIBuilder(url);
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept", "application/json");
        HttpResponse response = buildHttpClientAutoUpdateCertificate().execute(httpGet);
        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
            throw new CustomException("操作失败");
        }
        return response;
    }

    /***
     * 构建httpclient
     * @return HttpClient
     * @throws Exception 异常
     */
    private HttpClient buildHttpClient() throws Exception {
        ClassPathResource resource = new ClassPathResource("证书所在路径");
        X509Certificate weChatPayCertificate = PemUtil
                .loadCertificate(resource.getInputStream());
        return WechatPayHttpClientBuilder.create()
                .withMerchant(wxConfig.getMchId(), wxConfig.getSerialNo(), getPrivateKey())
                .withWechatpay(Collections.singletonList(weChatPayCertificate))//证书
                .build();
    }

    /***
     * 构建httpclient（自动更新证书）
     * @return HttpClient
     */
    private HttpClient buildHttpClientAutoUpdateCertificate() throws IOException {
        //不需要传入微信支付证书了
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(wxConfig.getMchId(), new PrivateKeySigner(wxConfig.getSerialNo(), getPrivateKey())),
                wxConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        return WechatPayHttpClientBuilder.create()
                .withMerchant(wxConfig.getMchId(), wxConfig.getSerialNo(), getPrivateKey())
                .withValidator(new WechatPay2Validator(verifier))
                .build();
    }

    /**
     * 获取私钥。
     *
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey() throws IOException {
        ClassPathResource resource = new ClassPathResource("private_key.pem");
        String content = new String(Files.readAllBytes(Paths.get(resource.getPath())), StandardCharsets.UTF_8);
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", StringUtils.EMPTY)
                    .replaceAll("\\s+", StringUtils.EMPTY);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }
}